# This file is part of the AutoMAT distribution (https://bitbucket.com/mahomaho/AutoMAT).
# Copyright (c) 2021 Mattias Holmqvist.
# 
# AutoMAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# AutoMAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with AutoMAT.  If not, see <https://www.gnu.org/licenses/>.

import re

from AutoMAT import *

from .support import *
from . import complexbase


def __repr__EcucParameterValue_EcucAbstractReferenceValue(self):
	try:
		assert self.definition.ref().parent() is self.parent().definition.ref()
		binding=self.ecucBinding()
		ret=self.parent().__repr__()+'.'+binding.name()
		if self.ecucBinding().upperMultiplicity()>1:
			return ret+f'[{binding.index(self)}]'
		return ret
	except:
		b = self.binding()
		return f'{self.parent().__repr__()}.{b.name()}[{b.index(self)}]'
autosar_r4p0.Group.EcucAbstractReferenceValue.__repr__=__repr__EcucParameterValue_EcucAbstractReferenceValue
autosar_r4p0.Group.EcucParameterValue.__repr__=__repr__EcucParameterValue_EcucAbstractReferenceValue

def ref_EcucReferenceValue(self):
	return self.value._ref
autosar_r4p0.Group.EcucReferenceValue.ref=ref_EcucReferenceValue

def iref_EcucInstanceReferenceValue(self):
	context = self.value.contextElement.ref()
	target = self.value.target.ref()
	if context and target:
		return complexbase.InstanceRef(list(context),target)
	return ModelNone
autosar_r4p0.Group.EcucInstanceReferenceValue.iref=iref_EcucInstanceReferenceValue

def val_EcucInstanceReferenceValue(self):
	return self.iref().__str__()
autosar_r4p0.Group.EcucInstanceReferenceValue.val=val_EcucInstanceReferenceValue

#autosar_r4p0.Group.EcucIndexableValue #alla
#AutoMAT.autosar_r4p0.Group.EcucContainerValue #containers
#autosar_r4p0.Group.EcucAbstractReferenceValue #refar
#autosar_r4p0.Group.EcucParameterValue #values

def __dir__EcucModuleConfigurationValues(self):
	ret=super(autosar_r4p0.Group.EcucModuleConfigurationValues,self).__dir__()
	for dchild in self.definition.ref().container:
		name = dchild.shortName.val()
		if getattr(self,name):
			ret.append(name)
	return ret
autosar_r4p0.Group.EcucModuleConfigurationValues.__dir__=__dir__EcucModuleConfigurationValues
def __dir__EcucContainerValue(self):
	ret=super(autosar_r4p0.Group.EcucContainerValue,self).__dir__()
	_def=self.definition.ref()
	for dchild in _def.subContainer + _def.parameter + _def.reference:
		name = dchild.shortName.val()
		if getattr(self,name):
			ret.append(name)
	return ret
autosar_r4p0.Group.EcucContainerValue.__dir__=__dir__EcucContainerValue

def __getattr__EcucModuleConfigurationValues(self,name):
	_def=self.definition.ref()
	pdef=getattr(_def,name,None)
	if not isinstance(pdef,autosar_r4p0.Group.EcucDefinitionElement):
		if getattr(_def.refinedModuleDef.ref(), name, None):
			# container exists in autosar
			return ModelNone
		raise AttributeError(name)
	if not pdef.upperMultiplicityInfinite.val() and default(1,pdef).upperMultiplicity.val()==1:
		return next((child for child in self.container if child.definition.ref() is pdef),ModelNone)
	else:
		def generator():
			if pdef.requiresIndex.val():
				children=sorted((child for child in self.container if child.definition.ref() is pdef),key=lambda e:e.index.val() if e.index else 0xFFFFFFFF)
			else:
				children=sorted((child for child in self.container if child.definition.ref() is pdef),key=lambda e:e.shortName.val())
			for child in children:
				yield child
		return complexbase.EcucList(generator,self,pdef)		
autosar_r4p0.Group.EcucModuleConfigurationValues.__getattr__=__getattr__EcucModuleConfigurationValues

def __getattr__EcucContainerValue(self,name):
	_def=self.definition.ref()
	pdef=getattr(_def,name,None)
	if not isinstance(pdef,autosar_r4p0.Group.EcucDefinitionElement):
		# check if parameter in autosar def
		path = [name]
		cont = _def
		if not cont:
			raise AttributeError(name)
		while not isinstance(cont, autosar_r4p0.EcucModuleDef):
			path.append(cont.shortName.val())
			cont = cont.parent()
		cont = cont.refinedModuleDef.ref()
		for name in reversed(path):
			cont = getattr(cont,name,None)
			if not cont:
				raise AttributeError(name)
		# path exists in autosar, return ModelNone
		return ModelNone
	if not pdef.upperMultiplicityInfinite.val() and (not pdef.upperMultiplicity or pdef.upperMultiplicity.val()==1):
		if isinstance(pdef,autosar_r4p0.Group.EcucContainerDef):
			return next((child for child in self.subContainer if child.definition.ref() is pdef), ModelNone)
		elif isinstance(pdef,autosar_r4p0.Group.EcucParameterDef):
			try:
				return next((child for child in self.parameterValue if child.definition.ref() is pdef and child.value))
			except:
				try:
					if pdef.defaultValue:
						return complexbase.DefaultValueType(self,pdef)
				except:
					pass
				return ModelNone
		else:
			return next((child for child in self.referenceValue if child.definition.ref() is pdef and child.value), ModelNone)
	else:
		def generator():
			if isinstance(pdef,autosar_r4p0.Group.EcucContainerDef):
				if pdef.requiresIndex.val():
					children=sorted((child for child in self.subContainer if child.definition.ref() is pdef),key=lambda e:e.index.val() if e.index else 0xFFFFFFFF)
				else:
					children=sorted((child for child in self.subContainer if child.definition.ref() is pdef),key=lambda e:e.shortName.val())
			elif isinstance(pdef,autosar_r4p0.Group.EcucParameterDef):
				if pdef.requiresIndex.val():
					children=sorted((child for child in self.parameterValue if child.definition.ref() is pdef and child.value),key=lambda e:e.index.val() if e.index else 0xFFFFFFFF)
				else:
					children=sorted((child for child in self.parameterValue if child.definition.ref() is pdef and child.value),key=lambda e:e.val())
			else:
				if pdef.requiresIndex.val():
					children=sorted((child for child in self.referenceValue if child.definition.ref() is pdef and child.value),key=lambda e:e.index.val() if e.index else 0xFFFFFFFF)
				else:
					children=sorted((child for child in self.referenceValue if child.definition.ref() is pdef and child.value),key=lambda e:e.val())
			for child in children:
				yield child
		return complexbase.EcucList(generator,self,pdef)
autosar_r4p0.Group.EcucContainerValue.__getattr__=__getattr__EcucContainerValue

def references_Referrable(self):
	return ModelList(self._references.__iter__)
autosar_r4p0.Group.Referrable.references=references_Referrable

def _setup_Referrable(self):
	name=self.shortName.val()
	if name:
		parent=self._parent
		if not isinstance(parent, (autosar_r4p0.Group.Referrable, autosar_r4p0.Group.AUTOSAR)):
			parent=parent._parent
		if name in parent.__dict__:
			print(f"Warning: two containers with same path {str(self)} is not supported by automat since variation points not supported. Last loaded will be ignored")
			parent.__dict__[self._binding._childname]=list((c for c in self._parent.__dict__[self._binding._childname] if c is not self)) # cannot just pop becaue calling function accesses the list and then there will be an error due to modified list while looping through it
			return
		parent.__dict__[name]=self
	super(autosar_r4p0.Group.Referrable, self)._setup()
	path=str(self)
	try:
		missing=complexbase.ModelReference._missingReferences.pop(path)
		for m in missing:
			m._ref=self
		self._references.extend(missing)
	except:
		pass
	'''try:
		relmissings=complexbase.ModelReference._missingReferences.get(path.rsplit('/',1)[-1],())
		#for relmissing in relmissings[:]:
			# test if this could be the missing path, then pop from relmissings, append to _references
	except:
		pass'''
autosar_r4p0.Group.Referrable._setup=_setup_Referrable

def _unsetup_Referrable(self):
	super(autosar_r4p0.Group.Referrable, self)._unsetup()
	for ref in self._references:
		ref._ref=ModelNone
		refval=ref.val()
		if refval[0]=='/':
			# absolute ref:
			complexbase.ModelReference._missingReferences[refval].append(ref)
		else:
			# relative ref
			complexbase.ModelReference._missingReferences[refval.rsplit('/',1)[-1]].append(ref)
	self._references.clear()
	name=self.shortName.val()
	if name:
		parent=self._parent
		if not isinstance(parent, (autosar_r4p0.Group.Referrable, autosar_r4p0.Group.AUTOSAR)):
			parent=parent._parent
		del parent.__dict__[name]
autosar_r4p0.Group.Referrable._unsetup=_unsetup_Referrable

autosar_r4p0.Group.EcucIndexableValue.ecucAggregations=lambda self: complexbase.EcucAggregations(self)
autosar_r4p0.Group.EcucModuleConfigurationValues.ecucAggregations=autosar_r4p0.Group.EcucIndexableValue.ecucAggregations

def ecucBinding_EcucIndexableValue(self):
	return complexbase.EcucAggregation(self.parent(),self.definition.ref())
autosar_r4p0.Group.EcucIndexableValue.ecucBinding=ecucBinding_EcucIndexableValue

def module_EcucIndexableValue_EcucContainerValue(self):
	parent=self.parent()
	while not isinstance(parent,autosar_r4p0.EcucModuleConfigurationValues):
		parent=parent.parent()
	return parent
autosar_r4p0.Group.EcucIndexableValue.module=module_EcucIndexableValue_EcucContainerValue
autosar_r4p0.Group.EcucContainerValue.module=module_EcucIndexableValue_EcucContainerValue

def val_LimitValueVariationPoint(self):
	intervalType=self.intervalType
	import sys, math
	if intervalType is autosar_r4p0.SimpleTypes.IntervalTypeEnum.infinite:
		if self.binding().name() in ('lowerLimit','min'):
			return -math.inf
		return math.inf
	val=float(eval(self._xml[0].text,{'INF':math.inf}, {}))
	if intervalType is autosar_r4p0.SimpleTypes.IntervalTypeEnum.open:
		match self.binding().name():
			case 'lowerLimit' | 'min':
				return val+sys.float_info.epsilon
			case 'upperLimit' | 'max':
				return val-sys.float_info.epsilon
	return val
autosar_r4p0.Group.LimitValueVariationPoint.val=val_LimitValueVariationPoint

def validate_EcucIndexableValue(self):
	definition=self.definition.ref()
	assert definition!=None,'Definition is missing'
	assert definition.parent() is self.parent().definition.ref(), 'Configuration definition is not a child of parents definition. Is the configuration modified manually?'
	if isinstance(definition, autosar_r4p0.Group.EcucParameterDef) and definition.withAuto.val():
		assert self.isAutoValue.val(), 'Value without isAutoValue for property with withAuto set'
	definition._validate(self)
	for vc in definition._validationConds:
		vc(self)
autosar_r4p0.Group.EcucIndexableValue.validate=validate_EcucIndexableValue
def validate_EcucModuleConfigurationValues(self):
	definition=self.definition.ref()
	assert definition,'Definition is missing'
	if self.moduleDescription.ref():
		assert definition in self.moduleDescription.ref().vendorSpecificModuleDef.ref(), 'The definition model is not a valid definition for the referenced implementation'
		definition._validate(self)
		for vc in definition._validationConds:
			vc(self)
autosar_r4p0.Group.EcucModuleConfigurationValues.validate=validate_EcucModuleConfigurationValues
# EcucDefinitionElement
def _validate_EcucDefinitionElement(self,instance):
	pass
autosar_r4p0.Group.EcucDefinitionElement._validate=_validate_EcucDefinitionElement
def _validate_EcucIntegerParamDef(self,instance):
	val=instance.val()
	assert isinstance(val,int),f'Value {val} is not a valid integer'
	assert self.min==None or val >= self.min.val(), f'Value below min {self.min.val()}'
	assert self.max==None or val <= self.max.val(), f'Value above max {self.max.val()}'
autosar_r4p0.Group.EcucIntegerParamDef._validate=_validate_EcucIntegerParamDef
def _validate_EcucFloatParamDef(self,instance):
	val=instance.val()
	assert isinstance(val,float),f'Value {val} is not a valid float value'
	assert self.min==None or val >= self.min.val(), f'Value below min {self.min.val()}'
	assert self.max==None or val <= self.max.val(), f'Value above max {self.max.val()}'
autosar_r4p0.Group.EcucFloatParamDef._validate=_validate_EcucFloatParamDef
def _validate_EcucBooleanParamDef(self,instance):
	val=instance.val()
	assert isinstance(val,int) and int(val) in (0,1),f'Value {val} is not a valid boolean value'
autosar_r4p0.Group.EcucBooleanParamDef._validate=_validate_EcucBooleanParamDef
def _validate_EcucEnumerationParamDef(self,instance):
	val=instance.val()
	assert val in self.literal.shortName.val(), f'Value {val} not in valid range'
autosar_r4p0.Group.EcucEnumerationParamDef._validate=_validate_EcucEnumerationParamDef
def _validate_EcucLinkerSymbolDef(self,instance):
	val=instance.val()
	pattern='[a-zA-Z][a-zA-Z0-9_.$%]{0,254}'
	assert re.fullmatch(pattern, val), f'Value {val} does not fulfill regex {pattern}'
autosar_r4p0.Group.EcucLinkerSymbolDef._validate=_validate_EcucLinkerSymbolDef
def _validate_EcucFunctionNameDef(self,instance):
	val=instance.val()
	pattern='[a-zA-Z_][a-zA-Z0-9_]*'
	assert re.fullmatch(pattern, val), f'Value {val} does not fulfill regex {pattern}'
autosar_r4p0.Group.EcucFunctionNameDef._validate=_validate_EcucFunctionNameDef
def _validate_EcucStringParamDef_EcucMultilineStringParamDef(self,instance):
	val=instance.val()
	assert isinstance(val,str),f'Value {val} is not a valid string'
	pattern=self.regularExpression.val()
	if pattern:
		assert re.fullmatch(pattern, val), f'Value {val} does not fulfill regex {pattern}'
	assert not self.minLength or len(val) >= self.minLength.val(), f'String shorter than min length {self.minLength.val()}'
	assert not self.maxLength or len(val) <= self.maxLength.val(), f'String longer than max length {self.maxLength.val()}'
autosar_r4p0.Group.EcucStringParamDef._validate=_validate_EcucStringParamDef_EcucMultilineStringParamDef
autosar_r4p0.Group.EcucMultilineStringParamDef._validate=_validate_EcucStringParamDef_EcucMultilineStringParamDef
def _validate_EcucAbstractReferenceDef(self,instance):
	val=instance.ref()
	assert val!=None,f'referred object {instance.val()} not in loaded model'
	values=complexbase.EcucAggregation(instance.parent(),self).values()
	assert val in values, f'referred object not in valid list {[str(e) for e in values]}'
autosar_r4p0.Group.EcucAbstractReferenceDef._validate=_validate_EcucAbstractReferenceDef
def _validate_EcucInstanceReferenceDef(self,instance):
	val=instance.iref()
	assert val!=None,f'referred object {instance.val()} not in loaded model'
	values=complexbase.EcucAggregation(instance.parent(),self).values()
	assert val in values, f'referred object not in valid list {[str(e) for e in values]}'
autosar_r4p0.Group.EcucInstanceReferenceDef._validate=_validate_EcucInstanceReferenceDef
def _validate_EcucChoiceContainerDef(self,instance):
	numchilds=len(instance.subContainer)
	assert numchilds==1,f'Choice container {instance} must have one and only one child, found {numchilds}, {instance.subContainer}'
autosar_r4p0.Group.EcucChoiceContainerDef._validate=_validate_EcucChoiceContainerDef


class _AUTOSAR_AggregationType(ModelList):
	'AutosSAR model root'
	def __init__(self):
		def generator():
			yield autosar
		super().__init__(generator)
	def upperMultiplicity(self):
		return 1
	def lowerMultiplicity(self):
		return 1
	def name(self):
		return 'autosar'
	def parent(self):
		return ModelNone
	def sortable(self):
		return False
	def isProperty(self):
		return False
	def desttype(self):
		return autosar_r4p0.AUTOSAR
	def __dir__(self):
		return list(e for e in object.__dir__(self) if e[0] != '_')
	def __repr__(self):
		return 'autosar.binding()'
	def __hash__(self):
		return hash(autosar) ^ hash(_AUTOSAR_AggregationType)
	def validate(self):
		pass
_AUTOSAR_Aggregation=_AUTOSAR_AggregationType()
autosar_r4p0.AUTOSAR.binding = lambda self: _AUTOSAR_Aggregation
